package frame

import (
	"bytes"
	"encoding/binary"
	"errors"
	"gitee.com/lipore/go-nio/pkg/buffer"
	"hash/crc32"
)

// headerLengthBasedFrameCodec encode/decode the frame in format header+payload+crc32
// headerBuilder is used to build the header with length headerLength.
// headerPrefix is a list of byte to check if the frame is synced.
// lengthParser is used to parse the payload length.
// The frameLength should be headerLength + len(crc32) + lengthParser(header)
type headerLengthBasedFrameCodec struct {
	headerPrefix  []byte
	headerLength  int
	lengthParser  func(b []byte) int
	headerBuilder func(l int) []byte
}

func (h *headerLengthBasedFrameCodec) Decode(buf *buffer.Buffer) (Frame, bool, error) {
	byts := buf.Bytes()
	index := bytes.Index(byts, h.headerPrefix)
	if index > 0 {
		return nil, false, errors.New("header length based frame codec mis-sync")
	}
	if index == -1 {
		return nil, false, nil
	}
	l := h.lengthParser(byts[:h.headerLength])
	packetLen := l + h.headerLength + 4
	if buf.Len() >= packetLen {
		p := make([]byte, packetLen)
		_, err := buf.Read(p)
		if err != nil {
			return nil, false, err
		}
		checksum := binary.BigEndian.Uint32(p[l+h.headerLength : packetLen])
		expectCheckSum := crc32.ChecksumIEEE(p[:l+h.headerLength])
		if checksum != expectCheckSum {
			return nil, false, errors.New("header length based frame codec checksum mis-match")
		}
		return p[h.headerLength : h.headerLength+l], true, nil
	}
	return nil, false, nil
}

func (h *headerLengthBasedFrameCodec) Encode(f Frame) ([]byte, bool) {
	l := len(f)
	header := h.headerBuilder(l)
	byts := make([]byte, l+h.headerLength+4)
	copy(byts, header)
	copy(byts[h.headerLength:], f)
	checkSum := crc32.ChecksumIEEE(byts[:l+h.headerLength])
	binary.BigEndian.PutUint32(byts[l+h.headerLength:], checkSum)

	return byts, true
}

func HeaderLengthBasedFrameCodec(headerPrefix []byte, headerLength int, lengthParser func([]byte) int, headerBuilder func(int) []byte) CodeC {
	return &headerLengthBasedFrameCodec{
		headerPrefix:  headerPrefix,
		headerLength:  headerLength,
		lengthParser:  lengthParser,
		headerBuilder: headerBuilder,
	}
}
